" core module

function! QpEqualQ(args)
  return BoolNew(EqualQ(a:args[0], a:args[1]))
endfunction

function! QpLt(args)
  return BoolNew(ObjValue(a:args[0]) < ObjValue(a:args[1]))
endfunction

function! QpLte(args)
  return BoolNew(ObjValue(a:args[0]) <= ObjValue(a:args[1]))
endfunction

function! QpGt(args)
  return BoolNew(ObjValue(a:args[0]) > ObjValue(a:args[1]))
endfunction

function! QpGte(args)
  return BoolNew(ObjValue(a:args[0]) >= ObjValue(a:args[1]))
endfunction

function! QpAdd(args)
  return IntegerNew(ObjValue(a:args[0]) + ObjValue(a:args[1]))
endfunction

function! QpSub(args)
  return IntegerNew(ObjValue(a:args[0]) - ObjValue(a:args[1]))
endfunction

function! QpMul(args)
  return IntegerNew(ObjValue(a:args[0]) * ObjValue(a:args[1]))
endfunction

function! QpDiv(args)
  return IntegerNew(ObjValue(a:args[0]) / ObjValue(a:args[1]))
endfunction

function! QpTimeMs(args)
  return IntegerNew(0 + system('date +%s%N | cut -b1-13'))
endfunction

function! QpList(args)
  return ListNew(a:args)
endfunction

function! QpListQ(args)
  return BoolNew(ListQ(a:args[0]))
endfunction

function! QpVector(args)
  return VectorNew(a:args)
endfunction

function! QpVectorQ(args)
  return BoolNew(VectorQ(a:args[0]))
endfunction

function! QpSequentialQ(args)
  return BoolNew(SequentialQ(a:args[0]))
endfunction

function! QpHashMap(args)
  return HashBuild(a:args)
endfunction

function! QpMapQ(args)
  return BoolNew(HashQ(a:args[0]))
endfunction

function! QpEmptyQ(args)
  return BoolNew(EmptyQ(a:args[0]))
endfunction

function! QpCount(args)
  return IntegerNew(ListCount(a:args[0]))
endfunction

function! QpAssoc(args)
  let hash = copy(ObjValue(a:args[0]))
  let new_elements = HashBuild(a:args[1:])
  call extend(hash, ObjValue(new_elements))
  return HashNew(hash)
endfunction

function! QpDissoc(args)
  let hash = copy(ObjValue(a:args[0]))
  for keyobj in a:args[1:]
    let key = HashMakeKey(keyobj)
    if has_key(hash, key)
      call remove(hash, key)
    endif
  endfor
  return HashNew(hash)
endfunction

function! QpGet(args)
  if !HashQ(a:args[0])
    return g:QpNil
  endif
  let hash = ObjValue(a:args[0])
  let key = HashMakeKey(a:args[1])
  return get(hash, key, g:QpNil)
endfunction

function! QpContainsQ(args)
  if !HashQ(a:args[0])
    return FalseNew()
  endif
  let hash = ObjValue(a:args[0])
  let key = HashMakeKey(a:args[1])
  return BoolNew(has_key(hash, key))
endfunction

function! QpKeys(args)
  let listobjs = []
  for keyname in keys(ObjValue(a:args[0]))
    let keyobj = HashParseKey(keyname)
    call add(listobjs, keyobj)
  endfor
  return ListNew(listobjs)
endfunction

function! QpVals(args)
  return ListNew(values(ObjValue(a:args[0])))
endfunction

function! QpPrStr(args)
  return StringNew(join(map(copy(a:args), 'PrStr(v:val, 1)'), " "))
endfunction

function! QpStr(args)
  return StringNew(join(map(copy(a:args), 'PrStr(v:val, 0)'), ""))
endfunction

function! QpPrn(args)
  call PrintLn(join(map(copy(a:args), 'PrStr(v:val, 1)'), " "))
  return g:QpNil
endfunction

function! QpPrintLn(args)
  call PrintLn(join(map(copy(a:args), 'PrStr(v:val, 0)'), " "))
  return g:QpNil
endfunction

function! QpReadString(args)
  return ReadStr(ObjValue(a:args[0]))
endfunction

function! QpReadLine(args)
  let [eof, line] = Readline(ObjValue(a:args[0]))
  return eof ? g:QpNil : StringNew(line)
endfunction

function! QpSlurp(args)
  let filename = ObjValue(a:args[0])
  let lines = readfile(filename, "b")
  return StringNew(join(lines, "\n"))
endfunction

function! QpCons(args)
  let items = copy(ObjValue(a:args[1]))
  call insert(items, a:args[0])
  return ListNew(items)
endfunction

function! QpConcat(args)
  let res = []
  for list in a:args
    let res = res + ObjValue(list)
  endfor
  return ListNew(res)
endfunction

function! QpFirst(args)
  return ListFirst(a:args[0])
endfunction

function! QpNth(args)
  return ListNth(a:args[0], ObjValue(a:args[1]))
endfunction

function! QpRest(args)
  return ListRest(a:args[0])
endfunction

function! QpApply(args)
  let funcobj = a:args[0]
  let rest = a:args[1:]
  if len(rest) == 0
    let funcargs = []
  elseif len(rest) == 1
    let funcargs = ObjValue(rest[-1])
  else
    let funcargs = rest[:-2] + ObjValue(rest[-1])
  endif
  if NativeFunctionQ(funcobj)
    return NativeFuncInvoke(funcobj, ListNew(funcargs))
  elseif FunctionQ(funcobj)
    return FuncInvoke(funcobj, ListNew(funcargs))
  else
    throw "Not a function"
  endif
endfunction

function! QpMap(args)
  let funcobj = a:args[0]
  let res = []
  for item in ObjValue(a:args[1])
    unlet! mappeditem
    if NativeFunctionQ(funcobj)
      let mappeditem = NativeFuncInvoke(funcobj, ListNew([item]))
    elseif FunctionQ(funcobj)
      let mappeditem = FuncInvoke(funcobj, ListNew([item]))
    else
      throw "Not a function"
    endif
    call add(res, mappeditem)
  endfor
  return ListNew(res)
endfunction

function! QpThrow(args)
  unlet! g:QpExceptionObj
  let g:QpExceptionObj = a:args[0]
  throw "__QpException__"
endfunction

function! QpNilQ(args)
  return BoolNew(NilQ(a:args[0]))
endfunction

function! QpSymbol(args)
  return SymbolNew(ObjValue(a:args[0]))
endfunction

function! QpSymbolQ(args)
  return BoolNew(SymbolQ(a:args[0]))
endfunction

function! QpKeyword(args)
  return KeywordNew(ObjValue(a:args[0]))
endfunction

function! QpKeywordQ(args)
  return BoolNew(KeywordQ(a:args[0]))
endfunction

function! ConjList(list, elements)
  let newlist = a:list
  for e in a:elements
    let newlist = QpCons([e, newlist])
  endfor
  return newlist
endfunction

function! ConjVector(vector, elements)
  let items = copy(ObjValue(a:vector))
  for e in a:elements
    call add(items, e)
  endfor
  return VectorNew(items)
endfunction

function! QpConj(args)
  if ListQ(a:args[0])
    return ConjList(a:args[0], a:args[1:])
  elseif ArrayQ(a:args[0])
    return ConjVector(a:args[0], a:args[1:])
  endif
endfunction

function! QpMeta(args)
  return ObjMeta(a:args[0])
endfunction

function! QpWithMeta(args)
  let obj = a:args[0]
  return ObjNewWithMeta(ObjType(obj), copy(ObjValue(obj)), a:args[1])
endfunction

function! QpAtom(args)
  return AtomNew(a:args[0])
endfunction

function! QpAtomQ(args)
  return BoolNew(AtomQ(a:args[0]))
endfunction

function! QpDeref(args)
  return ObjValue(a:args[0])
endfunction

function! QpResetBang(args)
  return ObjSetValue(a:args[0], a:args[1])
endfunction

function! QpSwapBang(args)
  let atomval = ObjValue(a:args[0])
  let funcobj = a:args[1]
  let args = a:args[2:]
  let res = QpApply([funcobj, ListNew([atomval] + args)])
  return ObjSetValue(a:args[0], res)
endfunction

function! VimToQp(e)
  if type(a:e) == type(0)
    return IntegerNew(a:e)
  elseif type(a:e) == type(0.0)
    return FloatNew(a:e)
  elseif type(a:e) == type("")
    return StringNew(a:e)
  elseif type(a:e) == type([])
    let res = []
    for v in a:e
      call add(res, VimToQp(v))
    endfor
    return ListNew(res)
  elseif type(a:e) == type({})
    let res = {}
    for [k,v] in items(a:e)
      let keystring = HashMakeKey(StringNew(k))
      let res[keystring] = VimToQp(v)
    endfor
    return HashNew(res)
  else
    return g:QpNil
  endif
endfunction

function! QpVimStar(args)
  let vimexpr = ObjValue(a:args[0])
  let vimres = eval(vimexpr)
  return VimToQp(vimres)
endfunction

let CoreNs = {
  \ "=":           NewNativeFn("QpEqualQ"),
  \ "<":           NewNativeFn("QpLt"),
  \ "<=":          NewNativeFn("QpLte"),
  \ ">":           NewNativeFn("QpGt"),
  \ ">=":          NewNativeFn("QpGte"),
  \ "+":           NewNativeFn("QpAdd"),
  \ "-":           NewNativeFn("QpSub"),
  \ "*":           NewNativeFn("QpMul"),
  \ "/":           NewNativeFn("QpDiv"),
  \ "not":         NewNativeFn("QpNilQ"),
  \ "symbol-p":    NewNativeFn("QpSymbolQ"),
  \ "kword":       NewNativeFn("QpKeyword"),
  \ "kword-p":     NewNativeFn("QpKeywordQ"),
  \ "list":        NewNativeFn("QpList"),
  \ "cons-p":      NewNativeFn("QpListQ"),
  \ "array":       NewNativeFn("QpVector"),
  \ "array-p":     NewNativeFn("QpVectorQ"),
  \ "table":       NewNativeFn("QpHashMap"),
  \ "str":         NewNativeFn("QpStr"),
  \ "say":         NewNativeFn("QpPrintLn"),
  \ "cons":        NewNativeFn("QpCons"),
  \ "concat":      NewNativeFn("QpConcat"),
  \ "apply":       NewNativeFn("QpApply"),
  \ "map":         NewNativeFn("QpMap"),
  \ "raise":       NewNativeFn("QpThrow"),
  \ "vim*":        NewNativeFn("QpVimStar")
  \ }
